package top.hmtools.wxmp.core.access_handle;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import top.hmtools.wxmp.core.configuration.WxmpConfiguration;
import top.hmtools.wxmp.core.httpclient.HttpClientFactoryTools;
import top.hmtools.wxmp.core.model.AccessTokenBean;

public abstract class BaseAccessTokenHandle {

	protected Logger logger = LoggerFactory.getLogger(this.getClass());
	
	private String get_token_uri = "cgi-bin/token";
	
	/**
	 * 全局通用的配置类
	 */
	protected WxmpConfiguration wxmpConfiguration;

	/**
	 * 获取全局配置
	 * @return
	 */
	public WxmpConfiguration getWxmpConfiguration() {
		return wxmpConfiguration;
	}

	/**
	 * 设置全局配置
	 * @param wxmpConfiguration
	 */
	public void setWxmpConfiguration(WxmpConfiguration wxmpConfiguration) {
		this.wxmpConfiguration = wxmpConfiguration;
	}

	/**
	 * 获取可用的 access token 字符串
	 * <br>当设置了强制使用的accessToken字符串时，则使用配置类中的该属性值 {@link top.hmtools.wxmp.core.configuration.WxmpConfiguration}
	 * @return
	 */
	public String getAccessTokenString() {
		String forceAccessTokenString = this.wxmpConfiguration.getForceAccessTokenString();
		if(StringUtils.isNotBlank(forceAccessTokenString)){
			this.logger.info("因手动设置了强制使用的accessToken，故取该值：{}",forceAccessTokenString);
			return forceAccessTokenString;
		}else{
			return this.getAccessToken().getAccess_token();
		}
	}
	
	/**
	 * 获取可用的 access token
	 * <p>
	 * 会先从外部存储获取，如果为null，或者时间过期，那么则会重新从微信服务器获取，并写入外部存储
	 * </p>
	 * @return
	 */
	public AccessTokenBean getAccessToken(){
		AccessTokenBean accessTokenBean = this.getAccessTokenBeanFromStorage();
		//如果从外部存储获取accessToken 为null，则刷新一次（重新获取）
		if(accessTokenBean == null){
			this.refreshAccessToken();
			accessTokenBean = this.getAccessTokenBeanFromStorage();
		}
		//如果从外部存储获取accessToken 已过期，则刷新一次（重新获取）
		if(!this.isExpiresIn(accessTokenBean)){
			this.refreshAccessToken();
			accessTokenBean = this.getAccessTokenBeanFromStorage();
		}
		return accessTokenBean;
	}
	
	/**
	 * 直接从微信服务器获取access token，且不存入外部存储。
	 * <b>不建议直接使用此方法获取，因为 access token可用数是有限的</b>
	 * @return
	 */
	public AccessTokenBean getAccessTokenDirectFromWxServer(){
		//从微信服务器获取accessToken
		CloseableHttpClient httpClient = HttpClientFactoryTools.getPoolHttpClient();
		String appid = this.wxmpConfiguration.getAppid();
		String appsecret = this.wxmpConfiguration.getAppsecret();
		if(StringUtils.isAnyBlank(appid,appsecret)){
			throw new IllegalArgumentException("appid 或者 appsecret 为空，无法获取 accessToken");
		}
		HttpGet get = new HttpGet("https://"+this.wxmpConfiguration.geteUrlServer()+"/"+this.get_token_uri+"?grant_type=client_credential&appid="+appid+"&secret="+appsecret);
		try {
			CloseableHttpResponse response = httpClient.execute(get);
			InputStream content = response.getEntity().getContent();
			String contentStr = IOUtils.toString(content, "utf-8");
			JSONObject jsonObject = JSON.parseObject(contentStr);
			String access_token= jsonObject.getString("access_token");
			long expires_in = jsonObject.getLongValue("expires_in");
			
			AccessTokenBean accessTokenBean = new AccessTokenBean();
			accessTokenBean.setAccess_token(access_token);
			accessTokenBean.setAppid(appid);
			accessTokenBean.setExpires_in(expires_in);
			accessTokenBean.setLastModifyDatetime(new Date());
			accessTokenBean.setSecret(appsecret);
			if(this.logger.isDebugEnabled()){
				this.logger.debug("从微信服务器处获取到的accessToken信息是：{}",accessTokenBean);
			}
			return accessTokenBean;
		} catch (IOException e) {
			throw new RuntimeException("从微信服务器获取access Token异常：", e);
		}
	}
	
	/**
	 * 刷新 AccessToken，重新获取AccessToken后，会写入外部存储
	 * <br>线程安全的
	 */
	public synchronized void refreshAccessToken(){
		AccessTokenBean accessTokenBean = this.getAccessTokenDirectFromWxServer();
		this.saveAccessTokenBeanToStorage(accessTokenBean);
	}
	
	/**
	 * 从外部存储获取accessToken
	 * @return
	 */
	abstract public AccessTokenBean getAccessTokenBeanFromStorage();
	
	/**
	 * 将获取的 accessToken 信息存入外部存储
	 * @param accessTokenBean
	 */
	abstract public void saveAccessTokenBeanToStorage(AccessTokenBean accessTokenBean);
	
	/**
	 * 验证是否过期
	 * <br>当前时间 减去 上次获取accessToken的时间间隔 大于 accessToken的有效时间时，则认为是过期
	 * @return
	 */
	private boolean isExpiresIn(AccessTokenBean atBean){
		long expires_in = atBean.getExpires_in();
		Date lastModifyDatetime = atBean.getLastModifyDatetime();
		Date now = new Date();
		//当前时间 减去 上次获取accessToken的时间间隔 大于 accessToken的有效时间时，则认为是过期
		if((now.getTime()-lastModifyDatetime.getTime())>=expires_in*1000){
			return false;
		}else{
			return true;
		}
	}
}
